package com.bitin.adorn.common.plugin;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.Serializable;
import java.util.Date;
import java.util.concurrent.ThreadLocalRandom;

/**
 * 雪花算法ID生成器
 *
 * Twitter的Snowflake 算法<br>
 * 分布式系统中，有一些需要使用全局唯一ID的场景，有些时候我们希望能使用一种简单一些的ID，并且希望ID能够按照时间有序生成。
 *
 * <p>
 * snowflake的结构如下(每部分用-分开):<br>
 *
 * <pre>
 * 0 - 0000000000 0000000000 0000000000 0000000000 0 - 00000 - 00000 - 000000000000
 * </pre>
 * <p>
 * 第一位为未使用(符号位表示正数)，接下来的41位为毫秒级时间(41位的长度可以使用69年)<br>
 * 然后是5位datacenterId和5位workerId(10位的长度最多支持部署1024个节点）<br>
 * 最后12位是毫秒内的计数（12位的计数顺序号支持每个节点每毫秒产生4096个ID序号）
 * <p>
 * 并且可以通过生成的id反推出生成时间,datacenterId和workerId
 * <p>
 *
 * 参考：http://www.cnblogs.com/relucent/p/4955340.html
 *
 * @Author: Mr.Lu
 * @Datetime 2024/8/29 22:19
 */
public class Snowflake implements Serializable {
    private final Logger logger = LoggerFactory.getLogger(getClass());

    /**
     * 时间起始标记点，作为基准，一般取系统的最近时间（一旦确定不能变动）
     *
     * 初始时间戳(纪年)，可用雪花算法服务上线时间戳的值
     */
    private long twepoch = 1649059688068L;

    /* 机器标识位数 */

    /**
     * workId占用的位数
     */
    private final long workerIdBits = 5L;
    /**
     * dataCenterId占用的位数
     */
    private final long datacenterIdBits = 5L;
    /**
     * 序列号12位，代表每毫秒内可产生最大序列号，即 2^12 - 1 = 4095
     */
    private final long sequenceBits = 12L;

    /* 每一部分的最大值 */

    /**
     * 最大支持机器节点数0~31比特位，一共32个，最大值31
     * 0000000000000000000000000000000000000000000000000000000000011111
     */
    private final long maxWorkerId = -1L ^ (-1L << workerIdBits);
    /**
     * 最大支持数据中心节点数0~31比特位，一共32个，最大值31
     * 0000000000000000000000000000000000000000000000000000000000011111
     */
    private final long maxDatacenterId = -1L ^ (-1L << datacenterIdBits);
    /**
     * 序列掩码，用于限定序列最大值不能超过4095
     * 0000000000000000000000000000000000000000000000000000111111111111
     */
    private final long maxSequence = -1L ^ (-1L << sequenceBits);

    /* 每一部分向左的位移 */

    /**
     * 机器节点左移12位
     */
    private final long workerIdShift = sequenceBits;
    /**
     * 数据中心节点左移17位
     */
    private final long datacenterIdShift = sequenceBits + workerIdBits;
    /**
     * 时间毫秒数左移22位
     */
    private final long timestampShift = sequenceBits + workerIdBits + datacenterIdBits;

    /**
     * 数据中心ID
     */
    private long datacenterId;
    /**
     * 机器ID
     */
    private long workerId;
    /**
     * 并发控制,同一毫秒内的最新序号，最大值可为 2^12 - 1 = 4095
     */
    private long sequence = 4095L;
    /**
     * 记录上一次生产 ID 时间戳，主要用于判断是否同一毫秒，以及用于服务器时钟回拨判断
     */
    private long lastTimestamp = -1L;


    /**
     * 构造
     *
     * 创建Twitter的Snowflake 算法生成器。
     * <p>
     * 特别注意：此方法调用后会创建独立的对象，每个独立的对象ID不互斥，会导致ID重复，请自行保证单例！
     * </p>
     * 分布式系统中，有一些需要使用全局唯一ID的场景，有些时候我们希望能使用一种简单一些的ID，并且希望ID能够按照时间有序生成。
     *
     * <p>
     * snowflake的结构如下(每部分用-分开):<br>
     *
     * <pre>
     * 0 - 0000000000 0000000000 0000000000 0000000000 0 - 00000 - 00000 - 000000000000
     * </pre>
     * <p>
     * 第一位为未使用，接下来的41位为毫秒级时间(41位的长度可以使用69年)<br>
     * 然后是5位datacenterId和5位workerId(10位的长度最多支持部署1024个节点）<br>
     * 最后12位是毫秒内的计数（12位的计数顺序号支持每个节点每毫秒产生4096个ID序号）
     *
     * <p>
     * 参考：http://www.cnblogs.com/relucent/p/4955340.html
     *
     * @param workerId     终端ID
     * @param dataCenterId 数据中心ID
     */
    public Snowflake(long workerId, long dataCenterId) {
        this(null, workerId, dataCenterId);
    }

    /**
     * 构造
     *
     * @param epochDate        初始化时间起点（null表示默认起始日期）,后期修改会导致id重复,如果要修改连workerId dataCenterId，慎用
     * @param workerId         工作机器节点id
     * @param dataCenterId     数据中心id
     */
    public Snowflake(Date epochDate, long workerId, long dataCenterId) {
        if (null != epochDate) {
            this.twepoch = epochDate.getTime();
        }

        String message;

        if (datacenterId > maxDatacenterId || datacenterId < 0) {
            message = String.format("datacenter Id can't be greater than %d or less than 0", maxDatacenterId);
            logger.error(message);
        }

        // 检查workId的合法值
        if (workerId > maxWorkerId || workerId < 0) {
            message = String.format("workId值必须大于0并且小于%d", maxWorkerId);
            logger.error(message);
            throw new IllegalArgumentException(message);
        }

        // 检查datacenterId的合法值
        if (dataCenterId > maxDatacenterId || dataCenterId < 0) {
            message = String.format("datacenterId值必须大于0并且小于%d", maxDatacenterId);
            logger.error(message);
            throw new IllegalArgumentException();
        }

        this.workerId = workerId;
        this.datacenterId = dataCenterId;
    }

    /**
     * 根据Snowflake的ID，获取机器id
     *
     * @param id snowflake算法生成的id
     * @return 所属机器的id
     */
    protected long getWorkerId(long id) {
        return id >> workerIdShift & ~(-1L << workerIdBits);
    }

    /**
     * 根据Snowflake的ID，获取数据中心id
     *
     * @param id snowflake算法生成的id
     * @return 所属数据中心
     */
    protected long getDataCenterId(long id) {
        return id >> datacenterIdShift & ~(-1L << datacenterIdBits);
    }

    /**
     * 根据Snowflake的ID，获取生成时间
     *
     * @param id snowflake算法生成的id
     * @return 生成的时间
     */
    protected long getGenerateDateTime(long id) {
        return (id >> timestampShift & ~(-1L << 41L)) + twepoch;
    }

    /**
     * 通过雪花算法生成下一个id，注意这里使用synchronized同步
     *
     * @return 唯一id
     */
    public synchronized long nextId() {

        long currentTimestamp = timeGen();

        // 当前时间小于上一次生成id使用的时间，可能出现服务器时钟回拨问题
        if (currentTimestamp < lastTimestamp) {
            long offset = lastTimestamp - currentTimestamp;
            if(offset <= 5){
                try {
                    wait(offset << 1);
                    // 容忍2秒内的回拨，避免NTP校时造成的异常
                    currentTimestamp = timeGen();
                    if (currentTimestamp < lastTimestamp) {
                        throw new RuntimeException(String.format("Clock moved backwards.  Refusing to generate id for %d milliseconds", offset));
                    }
                } catch (Exception e) {
                    throw new RuntimeException(e);
                }
            } else{
                // 如果服务器时间有问题(时钟后退) 报错。
                throw new IllegalStateException(String.format("可能出现服务器时钟回拨问题，请检查服务器时间。当前服务器时间戳：%d，上一次使用时间戳：%d", currentTimestamp, lastTimestamp));
            }
        }

        if (currentTimestamp == lastTimestamp) {

            // 还是在同一毫秒内，则将序列号递增1，序列号最大值为4095
            // 序列号的最大值是4095，使用掩码（最低12位为1，高位都为0）进行位与运行后如果值为0，则自增后的序列号超过了4095
            // 那么就使用新的时间戳
            final long sequence = (this.sequence + 1) & maxSequence;
            if (sequence == 0) {
                currentTimestamp = tilNextMillis(lastTimestamp);
            }
            this.sequence = sequence;
        } else {
            // 不在同一毫秒内，则序列号置为 1 - 3 随机数(也可以从0)开始，序列号最大值为4095
            sequence = ThreadLocalRandom.current().nextLong(1, 3);
        }

        // 记录最后一次使用的毫秒时间戳
        this.lastTimestamp = currentTimestamp;

        // 核心算法，将不同部分的数值移动到指定的位置，然后进行或运行
        // <<：左移运算符, 1 << 2 即将二进制的 1 扩大 2^2 倍
        // |：位或运算符, 是把某两个数中, 只要其中一个的某一位为1, 则结果的该位就为1
        // 优先级：<< > |
        return
            // 时间戳部分
            ((currentTimestamp - twepoch) << timestampShift)
                // 数据中心部分
                | (datacenterId << datacenterIdShift)
                // 机器表示部分
                | (workerId << workerIdShift)
                // 序列号部分
                | sequence;
    }

    /**
     * 下一个ID（字符串形式）
     *
     * @return ID 字符串形式
     */
    public String nextIdStr() {
        return Long.toString(nextId());
    }

    /**
     * 获取指定时间戳的接下来的时间戳，也可以说是下一毫秒
     *
     * @param lastTimestamp 指定毫秒时间戳
     * @return 时间戳
     */
    private long tilNextMillis(long lastTimestamp) {
        long currentTimestamp = timeGen();
        // 循环直到操作系统时间戳变化
        while (currentTimestamp <= lastTimestamp) {
            currentTimestamp = timeGen();
        }
        if (currentTimestamp < lastTimestamp) {
            // 如果发现新的时间戳比上次记录的时间戳数值小，说明操作系统时间发生了倒退，报错
            throw new IllegalStateException(String.format("Clock moved backwards. Refusing to generate id for {}ms", lastTimestamp - currentTimestamp));
        }
        return currentTimestamp;
    }

    /**
     * 返回以毫秒为单位的当前时间
     * @return 当前时间(毫秒)
     */
    protected long timeGen() {
        return System.currentTimeMillis();
    }
}
